spb/spboucher.ai Public
spboucher.ai — personal website of Simon-Pierre Boucher.
TypeScript 93.4%
HTML 5.5%
CSS 1%
1/*2 page.tsx3 spboucher.ai Web4 Author: Simon-Pierre Boucher5 Mail: contact@spboucher.ai6*/78import type { Metadata } from "next";9import Link from "next/link";10import { notFound } from "next/navigation";11import {12 ArrowLeft,13 Braces,14 CheckCircle2,15 Database,16 FileText,17 FlaskConical,18 Github,19 ScrollText,20} from "lucide-react";2122import { Badge } from "@/components/ui/badge";23import { buttonVariants } from "@/components/ui/button";24import { Reveal } from "@/components/reveal";25import { SectionHeading } from "@/components/section-heading";26import { getResearchPaper, allResearchPapers } from "@/lib/research";27import { researchDetails } from "@/lib/research-details";2829interface PageProps {30 params: Promise<{ slug: string }>;31}3233export function generateStaticParams() {34 return allResearchPapers35 .filter((p) => researchDetails.some((d) => d.slug === p.slug))36 .map((p) => ({ slug: p.slug }));37}3839export async function generateMetadata({40 params,41}: PageProps): Promise<Metadata> {42 const { slug } = await params;43 const paper = getResearchPaper(slug);44 const detail = researchDetails.find((d) => d.slug === slug);45 if (!paper || !detail) return {};46 return {47 title: `${paper.title} — ${paper.num}`,48 description: detail.hero.subheadline,49 };50}5152export default async function ResearchDetailPage({ params }: PageProps) {53 const { slug } = await params;54 const paper = getResearchPaper(slug);55 const detail = researchDetails.find((d) => d.slug === slug);56 if (!paper || !detail) notFound();5758 return (59 <article className="mx-auto max-w-4xl px-4 py-16 sm:px-6">60 <Reveal>61 <Link62 href="/research"63 className="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.12em] text-muted-foreground transition-colors hover:text-primary"64 >65 <ArrowLeft className="h-3.5 w-3.5" aria-hidden="true" />66 All research67 </Link>68 </Reveal>6970 {/* Header */}71 <Reveal className="mt-10">72 <header>73 <p className="flex flex-wrap items-center gap-3 font-mono text-[0.68rem] font-medium uppercase tracking-[0.2em] text-primary">74 <span aria-hidden="true" className="h-px w-6 bg-primary/60" />75 {paper.num}76 {paper.pages && (77 <span className="text-muted-foreground/80">· {paper.pages}</span>78 )}79 {paper.status && (80 <span className="text-muted-foreground/80">· {paper.status}</span>81 )}82 </p>83 <h1 className="mt-4 font-display text-3xl font-semibold leading-tight tracking-tight sm:text-[2.6rem] sm:leading-[1.12]">84 {paper.title}85 </h1>86 <p className="mt-5 max-w-2xl font-display text-lg font-medium leading-snug tracking-tight text-foreground/85 [font-style:italic] sm:text-xl">87 {detail.hero.headline}88 </p>89 <p className="mt-3 max-w-2xl text-sm leading-relaxed text-muted-foreground">90 {detail.hero.subheadline}91 </p>92 <p className="mt-4 text-sm text-muted-foreground">93 Simon-Pierre Boucher —{" "}94 <a95 href="mailto:contact@spboucher.ai"96 className="text-primary hover:underline"97 >98 contact@spboucher.ai99 </a>100 </p>101 <div className="mt-6 flex flex-wrap gap-2">102 <a103 href={paper.pdf}104 target="_blank"105 rel="noopener noreferrer"106 className={buttonVariants({ size: "sm" })}107 >108 <FileText aria-hidden="true" />109 Paper PDF110 </a>111 {paper.repo && (112 <a113 href={paper.repo}114 target="_blank"115 rel="noopener noreferrer"116 className={buttonVariants({ variant: "outline", size: "sm" })}117 >118 <Github aria-hidden="true" />119 Code & data repository120 </a>121 )}122 {paper.source && (123 <a124 href={paper.source}125 target="_blank"126 rel="noopener noreferrer"127 className={buttonVariants({ variant: "outline", size: "sm" })}128 >129 <Braces aria-hidden="true" />130 LaTeX source131 </a>132 )}133 </div>134 </header>135 </Reveal>136137 {/* Headline findings */}138 {detail.findings.length > 0 && (139 <Reveal className="mt-14">140 <div className="grid grid-cols-2 gap-x-8 gap-y-8 sm:grid-cols-3">141 {detail.findings.map((f) => (142 <div key={f.label} className="border-t-2 border-foreground pt-3">143 <p className="font-display text-3xl font-semibold tracking-tight sm:text-4xl">144 {f.value}145 </p>146 <p className="mt-1.5 font-mono text-[0.66rem] uppercase leading-relaxed tracking-[0.12em] text-muted-foreground">147 {f.label}148 </p>149 </div>150 ))}151 </div>152 </Reveal>153 )}154155 {/* Abstract */}156 <section className="mt-14">157 <Reveal>158 <SectionHeading title="Abstract" icon={ScrollText} />159 </Reveal>160 <Reveal delay={0.06}>161 <div className="mt-5 max-w-3xl space-y-4 border-l border-border pl-5 sm:pl-6">162 {detail.abstract.map((paragraph) => (163 <p164 key={paragraph.slice(0, 48)}165 className="text-sm leading-relaxed text-muted-foreground sm:text-[0.95rem]"166 >167 {paragraph}168 </p>169 ))}170 </div>171 </Reveal>172 </section>173174 {/* Contributions */}175 {detail.contributions.length > 0 && (176 <section className="mt-14">177 <Reveal>178 <SectionHeading title="Key Results" icon={FlaskConical} />179 </Reveal>180 <ol className="mt-6 space-y-5">181 {detail.contributions.map((c, i) => (182 <Reveal key={c.title} delay={i * 0.05}>183 <li className="flex items-start gap-4 border-b border-border/60 pb-5 last:border-0">184 <span185 aria-hidden="true"186 className="pt-0.5 font-mono text-sm font-semibold text-primary"187 >188 {String(i + 1).padStart(2, "0")}189 </span>190 <div className="min-w-0">191 <h3 className="font-display text-base font-semibold tracking-tight">192 {c.title}193 </h3>194 <p className="mt-1 max-w-2xl text-sm leading-relaxed text-muted-foreground">195 {c.description}196 </p>197 </div>198 </li>199 </Reveal>200 ))}201 </ol>202 </section>203 )}204205 {/* Data & methodology */}206 <div className="mt-14 grid gap-12 md:grid-cols-2 md:gap-10">207 {detail.data.length > 0 && (208 <section>209 <Reveal>210 <SectionHeading title="Data" icon={Database} />211 </Reveal>212 <div className="mt-5 space-y-4">213 {detail.data.map((d, i) => (214 <Reveal key={d.title} delay={i * 0.05}>215 <div className="border-l-2 border-primary/50 pl-4">216 <h3 className="text-sm font-semibold">{d.title}</h3>217 <p className="mt-1 text-sm leading-relaxed text-muted-foreground">218 {d.description}219 </p>220 </div>221 </Reveal>222 ))}223 </div>224 </section>225 )}226227 {detail.methodology.length > 0 && (228 <section>229 <Reveal>230 <SectionHeading title="Methodology" icon={FlaskConical} />231 </Reveal>232 <div className="mt-5 space-y-4">233 {detail.methodology.map((m, i) => (234 <Reveal key={m.title} delay={i * 0.05}>235 <div className="border-l-2 border-border pl-4">236 <h3 className="text-sm font-semibold">{m.title}</h3>237 <p className="mt-1 text-sm leading-relaxed text-muted-foreground">238 {m.description}239 </p>240 </div>241 </Reveal>242 ))}243 </div>244 </section>245 )}246 </div>247248 {/* Reproducibility */}249 {detail.reproducibility.length > 0 && (250 <section className="mt-14">251 <Reveal>252 <SectionHeading title="Reproducibility" icon={Github} />253 </Reveal>254 <ul className="mt-5 grid gap-3 sm:grid-cols-2">255 {detail.reproducibility.map((item, i) => (256 <Reveal key={item} delay={i * 0.05} className="h-full">257 <li className="flex h-full items-start gap-3 border border-border bg-card px-4 py-3.5 shadow-[var(--shadow-soft)]">258 <CheckCircle2259 className="mt-0.5 h-4 w-4 shrink-0 text-primary"260 aria-hidden="true"261 />262 <span className="text-sm leading-relaxed text-foreground/85">263 {item}264 </span>265 </li>266 </Reveal>267 ))}268 </ul>269 </section>270 )}271272 {/* Keywords + CTA */}273 <Reveal className="mt-14">274 <footer className="border-t-2 border-foreground pt-6">275 <div className="flex flex-wrap gap-1.5">276 {detail.keywords.map((k) => (277 <Badge key={k} variant="outline">278 {k}279 </Badge>280 ))}281 </div>282 <div className="mt-6 flex flex-wrap gap-2">283 <a284 href={paper.pdf}285 target="_blank"286 rel="noopener noreferrer"287 className={buttonVariants({ size: "sm" })}288 >289 <FileText aria-hidden="true" />290 Read the paper (PDF)291 </a>292 {paper.repo && (293 <a294 href={paper.repo}295 target="_blank"296 rel="noopener noreferrer"297 className={buttonVariants({ variant: "outline", size: "sm" })}298 >299 <Github aria-hidden="true" />300 Explore the repository301 </a>302 )}303 </div>304 </footer>305 </Reveal>306 </article>307 );308}309